Bokeh property editor¶
In [1]:
import numpy as np
import panel as pn
pn.extension()
Bokeh's property system defines the valid properties for all the different bokeh models. Using jslink we can easily tie a wiget value to bokeh properties on another widget or plot. This example defines two functions which generate a property editor for the most common bokeh properties. First we define two functions which generate a set of widgets linked to a plot:
In [2]:
from bokeh.core.enums import LineDash, LineCap, MarkerType, NamedColor
from bokeh.models.plots import Model, _list_attr_splat
def meta_widgets(model):
tabs = pn.Tabs(width=500)
widgets = get_widgets(model)
if widgets:
tabs.append((type(model).__name__, widgets))
for p, v in model.properties_with_values().items():
if isinstance(v, _list_attr_splat):
v = v[0]
if isinstance(v, Model):
subtabs = meta_widgets(v)
if subtabs is not None:
tabs.append((p.title(), subtabs))
if hasattr(model, 'renderers'):
for r in model.renderers:
tabs.append((type(r).__name__, meta_widgets(r)))
if hasattr(model, 'axis'):
for pre, axis in zip('XY', model.axis):
tabs.append(('%s-Axis' % pre, meta_widgets(axis)))
if hasattr(model, 'grid'):
for pre, grid in zip('XY', model.grid):
tabs.append(('%s-Grid' % pre, meta_widgets(grid)))
if not widgets and not len(tabs) > 1:
return None
elif not len(tabs) > 1:
return tabs[0]
return tabs
def get_widgets(model, skip_none=True, **kwargs):
widgets = []
for p, v in model.properties_with_values().items():
if isinstance(v, dict):
if 'value' in v:
v = v.get('value')
else:
continue
if v is None and skip_none:
continue
ps = dict(name=p, value=v, **kwargs)
if 'alpha' in p:
w = pn.widgets.FloatSlider(start=0, end=1, **ps)
elif 'color' in p:
if v in list(NamedColor):
w = pn.widgets.Select(options=list(NamedColor), **ps)
else:
w = pn.widgets.ColorPicker(**ps)
elif p.endswith('width'):
w = pn.widgets.FloatSlider(start=0, end=20, **ps)
elif 'marker' in p:
w = pn.widgets.Select(name=p, options=list(MarkerType), value=v)
elif p.endswith('cap'):
w = pn.widgets.Select(name=p, options=list(LineCap), value=v)
elif p == 'size':
w = pn.widgets.FloatSlider(start=0, end=20, **ps)
elif p.endswith('text') or p.endswith('label'):
w = pn.widgets.TextInput(**ps)
elif p.endswith('dash'):
patterns = list(LineDash)
w = pn.widgets.Select(name=p, options=patterns, value=v or patterns[0])
else:
continue
w.jslink(model, value=p)
widgets.append(w)
return pn.Column(*sorted(widgets, key=lambda w: w.name))
Having defined these helper functions we can now declare a plot and use the meta_widgets function to generate the GUI:
In [3]:
from bokeh.plotting import figure
p = figure(title='This is a title', x_axis_label='x-axis', y_axis_label='y-axis')
xs = np.linspace(0, 10)
r = p.scatter(xs, np.sin(xs))
pn.Row(p, meta_widgets(p))
Out[3]:
Download this notebook from GitHub (right-click to download).